import base64
import binascii
import copy
import datetime
import hashlib
import json
import random
import time
from decimal import Decimal

import redis
import requests
import rsa
from django.conf import settings
from django.db import models
from django_filters.rest_framework import FilterSet
from django_redis import get_redis_connection
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from rest_framework.utils.urls import replace_query_param, remove_query_param
from wechatpy import WeChatPay


def str_value(value):
    if not value:
        return ''
    else:
        value = str(value).strip()
    return value


def float_value(value, decimal=None):
    if not value:
        return 0
    try:
        res_value = float(value)
        if decimal:
            res_value = round(res_value, decimal)
    except (ValueError, TypeError):
        res_value = 0
    return res_value


def int_value(value):
    if not value:
        return 0
    try:
        res_value = round(float(value))
    except (ValueError, TypeError):
        res_value = 0
    return res_value


class NormalResultsSetPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'
    max_page_size = 100

    def get_next_url(self):
        if not self.page.has_next():
            return None
        url = self.request.get_full_path()
        page_number = self.page.next_page_number()
        return replace_query_param(url, self.page_query_param, page_number)

    def get_previous_url(self):
        if not self.page.has_previous():
            return None
        url = self.request.get_full_path()
        page_number = self.page.previous_page_number()
        if page_number == 1:
            return remove_query_param(url, self.page_query_param)
        return replace_query_param(url, self.page_query_param, page_number)

    def get_paginated_response(self, data):
        if not self.page.has_next():
            next_page = None
        else:
            next_page = self.page.next_page_number()
        return Response({
            # 'next_url': self.get_next_url(),
            # 'previous_url': self.get_previous_url(),
            'next_link': self.get_next_link(),
            'previous_link': self.get_previous_link(),
            'next_page': next_page,
            'current_length': len(data),
            'total_count': self.page.paginator.count,
            'page_num': self.page.number,
            'num_pages': self.page.paginator.num_pages,
            'page_size': self.page.paginator.per_page,
            'data': data,
            'message': 'ok',
            'code': 200
        })


class MmFilter(FilterSet):

    @classmethod
    def filter_for_field(cls, field, field_name, lookup_expr='exact'):
        filter_class = super().filter_for_field(field, field_name, lookup_expr)
        if lookup_expr == 'exact':
            filter_class.extra['help_text'] = '{0}等于'.format(field.verbose_name)
        elif lookup_expr == 'contains':
            filter_class.extra['help_text'] = '{0}包含'.format(field.verbose_name)
        elif lookup_expr == 'gte':
            filter_class.extra['help_text'] = '{0}大于等于'.format(field.verbose_name)
        elif lookup_expr == 'gt':
            filter_class.extra['help_text'] = '{0}大于'.format(field.verbose_name)
        elif lookup_expr == 'lt':
            filter_class.extra['help_text'] = '{0}小于'.format(field.verbose_name)
        elif lookup_expr == 'lte':
            filter_class.extra['help_text'] = '{0}小于等于'.format(field.verbose_name)
        return filter_class


def get_host(request):
    host = ''
    if request:
        headers = request.headers
        host = headers.get('Host')
    return host


def get_redis():
    """
    获取 Redis 的连接
    :return:
    """
    pool = get_redis_connection('default').connection_pool
    r = redis.Redis(connection_pool=pool)
    return r


def hset_data(name, key, value):
    # 将数据存储到 redis 缓存中
    r = get_redis()
    value = json.dumps(value)
    r.hset(name, key, value)


def hget_data(name, key):
    # 从 redis 缓存中获取数据
    r = get_redis()
    value = r.hget(name, key)
    if value:
        value = json.loads(value)
    return value


def set_data(name, value, **kwargs):
    # 将单条数据存入redis缓存
    r = get_redis()
    value = json.dumps(value)
    r.set(name, value, **kwargs)


def get_data(name):
    # 取出单条数据
    r = get_redis()
    value = r.get(name)
    if value:
        value = json.loads(value)
    return value


def convert_obj_to_dict(obj, fields=None, exclude=None):
    data = {}
    for f in getattr(obj, '_meta').concrete_fields + getattr(obj, '_meta').many_to_many:
        if f.name not in ['datetime_created', 'datetime_updated']:
            value = f.value_from_object(obj)

            if fields and f.name not in fields:
                continue

            if exclude and f.name in exclude:
                continue

            if isinstance(f, models.ManyToManyField):
                value = [i.id for i in value] if obj.pk else None

            elif isinstance(f, models.DateTimeField):
                value = value.strftime('%Y-%m-%d %H:%M:%S') if value else None

            elif isinstance(f, models.DateField):
                value = value.strftime('%Y-%m-%d') if value else None

            elif isinstance(f, models.TimeField):
                value = value.strftime('%H:%M:%S') if value else None

            elif isinstance(f, (models.ImageField, models.FileField)):
                if value:
                    value = str_value(value.url)
                else:
                    value = ''

            elif isinstance(f, (models.DecimalField, models.FloatField)):
                value = float_value(value)

            data[f.name] = value

    for p in getattr(getattr(obj, '_meta'), '_property_names'):
        if p not in ['pk']:
            value = getattr(obj, p)
            if isinstance(value, models.DateTimeField):
                value = value.strftime('%Y-%m-%d %H:%M:%S') if value else None

            elif isinstance(value, models.DateField):
                value = value.strftime('%Y-%m-%d') if value else None

            elif isinstance(value, models.TimeField):
                value = value.strftime('%H:%M:%S') if value else None

            elif isinstance(value, (models.DecimalField, models.FloatField)):
                value = float_value(value)
            data[p] = value
    return data


def model_data_from_cache(model, pk, force_update=False):
    """
    从缓存中获取数据，返回字典数据。
    :param model: 数据对应的 Model
    :param pk: 数据对应的 ID
    :param force_update: 是非强制更新
    :return: 返回序列化后的数据字典 {"id": 1, "field1": "field1_data"}
    """
    app_label = getattr(model, '_meta').app_label
    model_name = getattr(model, '_meta').model_name
    name = 'model_{}_{}'.format(app_label, model_name)
    pk = int_value(pk)
    if force_update:
        data = {}
    else:
        data = hget_data(name, pk)
    if not data:
        obj = model.objects.filter(pk=pk).first()
        if obj:
            value = convert_obj_to_dict(obj)
        else:
            value = {}
        hset_data(name, key=pk, value=value)
        data = hget_data(name, pk)
    return data


def check_mobile_code(mobile, code):
    if mobile in settings.TEST_MOBILE:
        if code in settings.TEST_CODE:
            return True
    # 短信验证码会过期，并且只能使用一次
    from app.models import SmsRecord
    send_time = datetime.datetime.now() - datetime.timedelta(days=1)
    sms_record = SmsRecord.objects.filter(
        mobile=mobile,
        code=code,
        datetime_updated__gte=send_time,
        is_active=True
    )
    print('sms_record', sms_record)
    if sms_record.exists():  # 使用此判断，用户的验证码只能使用一次
        sms_record.update(is_active=False)
        return True
    return False


def generate_code(m=6):
    str_string = list('0123456789')
    code = ''
    for i in range(m):
        code += random.choice(str_string)
    return code


def generate_keys():
    """
    生成公钥和私钥文件
    """
    public_key, private_key = rsa.newkeys(1024)
    pub = public_key.save_pkcs1()
    public_file = open('public.pem', 'wb')
    public_file.write(pub)
    public_file.close()
    pri = private_key.save_pkcs1()
    private_file = open('private.pem', 'wb')
    private_file.write(pri)
    private_file.close()


def rsa_encrypt(d_str):
    """
    对文本进行加密
    :param d_str: 文本
    :return: 加密后的数据
    """
    p = settings.PUBKEY.encode()
    public_key = rsa.PublicKey.load_pkcs1(p)
    # 将字符串进行编码
    content = d_str.encode('utf-8')
    # 公钥加密
    crypto = rsa.encrypt(content, public_key)
    return base64.b64encode(crypto).decode()


def rsa_decrypt(crypto):
    """
    对文本进行解密
    :param crypto: 密文
    :return: 解密后的数据
    """
    p = settings.PRIVKEY.encode()
    private_key = rsa.PrivateKey.load_pkcs1(p)
    # 解密
    content = rsa.decrypt(base64.b64decode(crypto), private_key)
    # 解码
    content = content.decode('utf-8')
    return content


def pbkdf2_hmac_encrypt(d_str):
    """
    单向加密数据
    :param d_str: 文本
    :return: 加密后的数据
    """
    dk = hashlib.pbkdf2_hmac('sha256', d_str.encode(), settings.SECRET_KEY.encode(), 100)
    crypto = binascii.hexlify(dk).decode()
    return crypto


def get_client_ip(request):
    if request.META.get('HTTP_X_FORWARDED_FOR'):
        ip = request.META.get('HTTP_X_FORWARDED_FOR')
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip


def make_wx_pay_order(openid, out_trade_no, total_fee, body='酒厂模拟器GVIP', client_ip='127.0.0.1'):
    """
    创建微信支付订单
    :param openid: 用户的 openid
    :param out_trade_no: 唯一订单号， 必填
    :param total_fee: 金额，以分为单位
    :param body: 商品描述，商品简单描述
    :param client_ip: 客户 IP
    :return: 返回从微信服务器获得的支付订单返回信息
    """
    wechat_pay = WeChatPay(
        appid=settings.WECHAT['APPID'],
        api_key=settings.WECHAT['API_KEY'],
        mch_id=settings.WECHAT['MCH_ID']
    )
    order = wechat_pay.order.create(
        trade_type=settings.WECHAT['TRADE_TYPE'],  # 交易类型，小程序取值：JSAPI
        out_trade_no=out_trade_no,
        body=body,  # 商品描述，商品简单描述
        total_fee=str(total_fee),  # 标价金额，订单总金额，单位为分
        notify_url=settings.WECHAT['NOTIFY_URL'],  # 通知地址，异步接收微信支付结果通知的回调地址，通知url不能携带参数。
        user_id=openid,  # 用户标识，trade_type=JSAPI，此参数必传，用户在商户appid下的唯一标识。
        client_ip=client_ip,
    )
    wx_pay_params = wechat_pay.jsapi.get_jsapi_params(order['prepay_id'])
    return wx_pay_params


def get_user_id(self):
    if self:
        if self.request:
            if self.request.user:
                if self.request.user.id:
                    return self.request.user.id
    return None


class APIResponse(Response):

    def __init__(self, data=None, message='ok', code=200, status=200, headers=None, exception=False, **kwargs):
        # res_data 的初始状态：状态码与状态信息
        res_data = {
            'data': data,
            'message': message,
            'code': code
        }
        res_data.update(kwargs)
        # 重写父类的Response的__init__方法
        super().__init__(data=res_data, status=status, headers=headers, exception=exception)


def get_order_code():
    current_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S%f')
    random_str = random.randrange(10, 99)
    order_code_main = '{}{}'.format(current_time, random_str)
    order_code_sum = 0
    for s in order_code_main:
        order_code_sum += int(s)
    cc = 100 - order_code_sum % 100
    order_code = '{}{}'.format(order_code_main, str(cc).zfill(2))
    return order_code


# class AddUserCreateAPIView(CreateAPIView):
#
#     def create(self, request, *args, **kwargs):
#         new_data = dict()
#         for k, v in request.data.items():
#             new_data[k] = v
#         user_id = get_user_id(self)
#         new_data[user_id] = user_id
#         serializer = self.get_serializer(data=new_data)
#         serializer.is_valid(raise_exception=True)
#         self.perform_create(serializer)
#         headers = self.get_success_headers(serializer.data)
#         return Response(serializer.data, status=201, headers=headers)


def file_field_to_link(field):
    res = ''
    if field:
        res = 'http://{}/{}'.format(settings.QINIU_BUCKET_DOMAIN, field)
    return res


class WeChatMsg:

    access_token = None
    req_list = list()
    user_list = list()
    data_list = list()
    template = {
        "touser": "",
        "template_id": "fmO6xXBe9hxQDaqxvK8RvBfInOZlb0NtS2mk1gJ5VjY",
        "url": "",
        "data": {
            "thing1": {
                "value": "本周兑换提醒",
            },
            "date4": {
                "value": "2021-06-27",
            },
            "thing5": {
                "value": "本周兑换即将结束，请尽快去兑换，以免错过!",
            },
        }
    }

    def __init__(self, app_id=settings.WX_APP_ID, secret=settings.WX_APP_SECRET, template=None):
        self.app_id = app_id
        self.secret = secret
        if template:
            self.template = template

    def get_token(self, force_update=False):
        key_name = 'wechat_token_{}'.format(self.app_id)
        if force_update:
            self.access_token = None
        else:
            self.access_token = get_data(key_name)
        if not self.access_token:
            url = "https://api.weixin.qq.com/cgi-bin/token?"
            payload = {
                'grant_type': 'client_credential',
                'appid': self.app_id,
                'secret': self.secret,
            }
            response = requests.get(url, params=payload, timeout=50)
            access_token = response.json().get("access_token")
            if access_token:
                set_data(key_name, access_token, ex=7100)
                self.access_token = get_data(key_name)
        print('access_token', self.access_token)

    def make_data_list(self):
        from app.models import WxUser
        user_openids = WxUser.objects.filter(
            pk__in=self.user_list
        ).exclude(
            openid=''
        ).exclude(
            openid__isnull=True
        ).values_list('openid', flat=True)

        for openid in user_openids:
            user_template = copy.deepcopy(self.template)
            user_template['touser'] = openid
            self.data_list.append(user_template)

    def post_data(self):
        # 获取 token
        self.get_token()
        url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={}".format(self.access_token)

        # 准备数据
        if self.user_list:
            self.make_data_list()
        for data in self.data_list:
            json_template = json.dumps(data)
            print(json_template)
            res = requests.post(url, data=json_template)
            print(res.text)
